package sub

import (
	"bufio"
	"errors"
	"fmt"
	psnet "github.com/shirou/gopsutil/v3/net"
	"github.com/urfave/cli/v2"
	"github.com/yusufpapurcu/wmi"
	"lib/cmdutil"
	"lib/structs/ams"
	"lib/utils"
	"net"
	"os"
	"path/filepath"
	"strings"
)

func HandsUpCommand() *cli.Command {
	var comm = &cli.Command{
		Name:      ams.SayHi,
		Usage:     "发送举手信息，告诉服务端自己是谁",
		UsageText: fmt.Sprintf("eg: %s %s", ams.Macp, ams.SayHi),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:        "mainPath",
				Aliases:     []string{"mp"},
				Usage:       "指定主程序所在的路径，未指定的情况下默认使用路径",
				DefaultText: filepath.Join(ams.ClientFolder, ams.ClientMain),
			},
		},
	}
	comm.Action = func(ctx *cli.Context) error {
		mainFilePath := ctx.String("mainPath")
		m, err := getInfo(mainFilePath)
		if err != nil {
			return cli.Exit(fmt.Sprintf("获取举手信息时发生错误:[%v]", err), 1)
		}
		w := bufio.NewWriter(os.Stdout)
		_, _ = w.WriteString(utils.Obj2Str(m))
		_ = w.Flush()
		return nil
	}
	return comm
}

func getInfo(mainFilePath string) (m ams.PluginForHandsUp, err error) {
	if !utils.IsExistFile(mainFilePath) {
		err = fmt.Errorf("main file:[%s] is not exist", mainFilePath)
		return
	}
	stdout, stuerr, err := cmdutil.RunCommandArgs([]string{mainFilePath, "-v"}, cmdutil.CommandOption{})
	if err != nil {
		err = fmt.Errorf("get main version err：[%v],stuerr:[%v]", err, stuerr)
		return
	}
	ip, mac, err := getIpMac()
	if err != nil {
		err = fmt.Errorf("getIpMac err ：%v", err)
		return
	}
	diskSerial, err := getSystemDiskSerial()
	if err != nil {
		err = fmt.Errorf("getSystemDiskSerial err ：%v", err)
		return
	}
	board, bios, err := getMainBoardSerialNum()
	if err != nil {
		err = fmt.Errorf("getMainBoardSerial err ：%v", err)
		return
	}
	hostname, err := os.Hostname()
	if err != nil {
		err = fmt.Errorf("get hostname err ：%v", err)
		return
	}
	m = ams.PluginForHandsUp{
		MainMd5:         utils.GetFileMD5(mainFilePath),
		MainVersion:     stdout,
		AssetCode:       "",
		IP:              ip,
		ActiveMAC:       mac,
		BiosSerial:      bios,
		MainBoardSerial: board,
		HardDiskSerial:  diskSerial,
		Hostname:        hostname,
	}
	return m, nil
}

// getIpMac 获取当前活跃ipv4地址和其对应的网卡地址
func getIpMac() (ip, mac string, err error) {
	var (
		psn psnet.InterfaceStatList
	)
	if ip, err = getOutBoundIP(); err != nil {
		return
	}
	if psn, err = psnet.Interfaces(); err != nil {
		return
	}
	for _, item := range psn {
		if len(item.Addrs) == 2 {
			ipv4 := item.Addrs[1].Addr
			ipv4 = ipv4[:strings.LastIndex(ipv4, "/")]
			if ipv4 == ip {
				mac = item.HardwareAddr
				mac = strings.ReplaceAll(mac, ":", "-")
			}
		}
	}
	return
}

func getOutBoundIP() (ip string, err error) {
	var (
		conn net.Conn
	)
	conn, err = net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		return "", err
	}
	localAdd := conn.LocalAddr()
	ip = strings.Split(localAdd.String(), ":")[0]
	return ip, nil
}

type DiskPart struct {
	Bootable      bool   `json:"bootable,omitempty"`      // 指示计算机是否可以从此分区启动。
	BootPartition bool   `json:"bootPartition,omitempty"` // 分区是活动分区。 从硬盘启动时，操作系统使用活动分区。
	DiskIndex     uint32 `json:"diskIndex,omitempty"`     // 分区的磁盘的索引号
	Index         uint32 `json:"index,omitempty"`         // 分区的索引号
}
type PhysicsDiskInfo struct {
	InterfaceType string `json:"interfaceType,omitempty"` // eg:SCSI ,物理磁盘驱动器的类型 （IDE、sata）
	MediaType     string `json:"mediaType,omitempty"`     // eg:Fixed hard disk media ,由该设备使用或访问的媒体类型。
	SerialNumber  string `json:"serialNumber,omitempty"`  // eg:6479_A721_8068_6489. 由制造商分配的号来识别物理介质。
	Size          uint64 `json:"size,omitempty"`          // eg:256052966400 ,磁盘大小 单位字节 / 1024*1024*1024 才能得到G，
}

// getSystemDiskSerial 获取系统盘所在的物理磁盘的序列号
func getSystemDiskSerial() (serial string, err error) {
	var parts []DiskPart
	if err = wmi.Query(" SELECT * FROM Win32_DiskPartition ", &parts); err != nil {
		return "", err
	}
	var diskIndex uint32
	for _, item := range parts {
		if item.Bootable && item.BootPartition {
			diskIndex = item.DiskIndex
		}
	}
	var disks []PhysicsDiskInfo
	if err = wmi.Query(" SELECT * FROM Win32_DiskDrive ", &disks); err != nil {
		return "", err
	}
	if diskIndex >= uint32(len(disks)) {
		return "", errors.New("超出下表范围")
	}
	serial = disks[diskIndex].SerialNumber
	return serial, nil
}

type Board struct {
	HostingBoard bool   `json:"hostingBoard,omitempty"` // eg:TRUE,如果为TRUE，该卡是一个主板，或在一个机箱中的基板。
	HotSwappable bool   `json:"hotSwappable,omitempty"` // eg:FALSE,如果为TRUE，就是支持热插拔（判断是否支持热插拔）
	Manufacturer string `json:"manufacturer,omitempty"` // eg:HASEE Computer,表示制造商的名称
	SerialNumber string `json:"serialNumber,omitempty"` // eg:00000000 ，经过测试大品牌机器这个值都是有唯一值的，我的是杂牌电脑 .制造商分配的用于识别所述物理元件数目。
}

// Bios for Win32_BIOS
type Bios struct {
	SerialNumber string `json:"serialNumber"` // eg:EJVPM21124000449 , 序列号
	Manufacturer string `json:"manufacturer"` // eg:HUAWEI , 制造商
}

func getMainBoardSerialNum() (boardSerial, biosSerial string, err error) {
	var board []Board
	var bios []Bios
	if err = wmi.Query("SELECT HostingBoard,HotSwappable,Manufacturer,SerialNumber FROM Win32_BaseBoard ", &board); err != nil {
		return
	}
	if err = wmi.Query("SELECT SerialNumber,Manufacturer FROM Win32_BIOS", &bios); err != nil {
		return
	}

	for _, v := range board {
		if v.HostingBoard {
			boardSerial = v.SerialNumber
		}
	}
	if len(bios) > 0 {
		biosSerial = bios[0].SerialNumber
	}
	return
}
